package com.yf.modules.system.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yf.Utils.CacheKey;
import com.yf.Utils.FileUtil;
import com.yf.Utils.RedisUtils;
import com.yf.dao.RoleMenuMapper;
import com.yf.dao.UserMapper;
import com.yf.domain.security.Menu;
import com.yf.dao.MenuMapper;
import com.yf.domain.security.Role;
import com.yf.domain.security.User;
import com.yf.modules.security.model.dto.MenuDto;
import com.yf.modules.system.model.dto.RoleSmallDto;
import com.yf.model.query.MenuQueryObject;
import com.yf.modules.system.model.vo.MenuMetaVo;
import com.yf.modules.system.model.vo.MenuVo;
import com.yf.modules.system.service.api.MenuService;
import com.yf.modules.system.service.api.RoleService;
import com.yf.response.exceptions.BusinessException;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author chentianwei
 * @date 2023/5/23 16:42
 * @description 菜单服务实现层
 */
@Service
@Slf4j
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {

    private final RoleService roleService;
    private final MenuMapper menuMapper;
    private final UserMapper userMapper;
    private final RedisUtils redisUtils;

    private final RoleMenuMapper roleMenuMapper;

    public MenuServiceImpl(RoleService roleService,
                           MenuMapper menuMapper,
                           UserMapper userMapper,
                           RedisUtils redisUtils,
                           RoleMenuMapper roleMenuMapper) {
        this.roleService = roleService;
        this.menuMapper = menuMapper;
        this.userMapper = userMapper;
        this.redisUtils = redisUtils;
        this.roleMenuMapper = roleMenuMapper;
    }

    private static final String HTTP_PRE = "http://";
    private static final String HTTPS_PRE = "https://";

    /**
     * 根据 当前登录的用户生成 当前用户的菜单信息
     *
     * @param currentUserId 当前用户的ID号
     * @return 菜单数据
     */
    @Override
    public List<MenuDto> getMenusByUsername(Long currentUserId) {


        List<RoleSmallDto> roles = roleService.findRolesByUserId(currentUserId);
        Set<Long> roleIds = roles.stream().map(RoleSmallDto::getId).collect(Collectors.toSet());

        QueryWrapper<Menu> wrapper = new QueryWrapper<>();
        wrapper.lambda().in(Menu::getId, roleIds);
        List<Menu> menus = menuMapper.selectList(wrapper);

        return BeanUtil.copyToList(menus, MenuDto.class);
    }

    /**
     * 通过 第一级获取到的MenuDto构建完整的MenuTree
     *
     * @param menuDtoList 各级MenusDto列表
     * @return 完整的MenuTree
     */
    @Override
    public List<MenuDto> buildTrees(List<MenuDto> menuDtoList) {

        List<MenuDto> trees = new ArrayList<>();
        Set<Long> ids = new HashSet<>();

        //递归遍历菜单
        for (MenuDto menuDto : menuDtoList) {
            //深度1的树
            if (menuDto.getPid() == null) {
                trees.add(menuDto);
            }
            //深度>1的树
            for (MenuDto deepMenuDto : menuDtoList) {
                //向下寻找,直到穷尽
                if (menuDto.getId().equals(deepMenuDto.getPid())) {
                    //没有孩子节点
                    if (menuDto.getChildren() == null) {
                        menuDto.setChildren(new ArrayList<>());
                    }
                    menuDto.getChildren().add(deepMenuDto);
                    ids.add(deepMenuDto.getId());
                }
            }
        }
        //获取空树
        if (trees.size() == 0) {
            trees = menuDtoList.stream().filter(s -> !ids.contains(s.getId())).collect(Collectors.toList());
        }
        return trees;
    }

    /**
     * 通过完整的MenuTree构建最终返回的类型
     *
     * @param menuDto 完整的MenuTree
     * @return 最终菜单
     */
    @Override
    public List<MenuVo> buildMenus(List<MenuDto> menuDto) {
        List<MenuVo> MenuList = new LinkedList<>();

        menuDto.forEach(element -> {
            if (ObjectUtil.isNotEmpty(element)) {
                List<MenuDto> children = element.getChildren();
                MenuVo vo = new MenuVo();
                //获取名称
                vo.setName(ObjectUtil.isNotEmpty(element.getComponentName()) ? element.getComponentName() : element.getTitle());
                //获取路径(如果是一级菜单,需要带上斜杠)
                vo.setPath(ObjectUtil.isEmpty(element.getPid()) ? "/" + element.getPath() : element.getPath());
                //获取是否隐藏
                vo.setHidden(element.getHidden());
                //获取小元素
                vo.setMeta(new MenuMetaVo(element.getTitle(), element.getIcon(), !element.getCache()));
                //非外链
                if (!element.getIFrame()) {
                    if (element.getPid() == null) {
                        vo.setComponent(StrUtil.isEmpty(element.getComponent()) ? "Layout" : element.getComponent());
                        //如果不是一级菜单，并且菜单类型为目录，则是多级菜单
                    } else if (element.getType() == 0) {
                        vo.setComponent(StrUtil.isEmpty(element.getComponent()) ? "ParentView" : element.getComponent());
                    } else if (StrUtil.isNotBlank(element.getComponent())) {
                        vo.setComponent(element.getComponent());
                    }
                }
                //遍历孩子节点
                if (CollectionUtil.isNotEmpty(children)) {
                    vo.setAlwaysShow(true);
                    vo.setRedirect("noredirect");
                    vo.setChildren(buildMenus(children));
                    // 处理是一级菜单并且没有子菜单的情况
                } else if (element.getPid() == null) {
                    MenuVo menuVo1 = new MenuVo();
                    menuVo1.setMeta(vo.getMeta());
                    // 非外链
                    if (!element.getIFrame()) {
                        menuVo1.setPath("index");
                        menuVo1.setName(vo.getName());
                        menuVo1.setComponent(vo.getComponent());
                    } else {
                        menuVo1.setPath(element.getPath());
                    }
                    vo.setName(null);
                    vo.setMeta(null);
                    vo.setComponent("Layout");
                    List<MenuVo> list1 = new ArrayList<>();
                    list1.add(menuVo1);
                    vo.setChildren(list1);
                }
                MenuList.add(vo);
            }
        });
        return MenuList;
    }

    /**
     * 添加新菜单
     *
     * @param resource 菜单实体
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addMenu(Menu resource) {
        if (menuMapper.findByTitle(resource.getTitle()) != null) {
            throw new BusinessException("Title" + resource.getTitle() + "已存在,请重新输入");
        }
        if (StrUtil.isNotBlank(resource.getComponentName())) {
            if (menuMapper.findByComponentName(resource.getComponentName()) != null) {
                throw new BusinessException("ComponentName:" + resource.getComponentName() + "已存在,请重新输入");
            }
        }
        if (Long.valueOf(0L).equals(resource.getPid())) {
            resource.setPid(null);
        }
        if (resource.getIFrame()) {
            if (!(resource.getPath().toLowerCase().startsWith(HTTP_PRE) || resource.getPath().toLowerCase().startsWith(HTTPS_PRE))) {
                throw new BusinessException("外链必须以http://或者https://开头");
            }
        }
        save(resource);
        //计算子节点的数量
        resource.setSubCount(0);
        //更新父节点菜单数目
        if (resource.getPid() != null) {
            int countByPid = menuMapper.countByPid(resource.getPid());
            menuMapper.updateSubCntById(countByPid, resource.getPid());
        }
    }

    /**
     * 修改菜单
     *
     * @param resource 菜单实体
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(Menu resource) {
        //预防修改自己的Id
        if (resource.getId().equals(resource.getPid())) {
            throw new BusinessException("上级菜单不可为自身");
        }
        //使用外链的情况
        if (resource.getIFrame()) {
            if (!(resource.getPath().toLowerCase().startsWith(HTTP_PRE) || resource.getPath().toLowerCase().startsWith(HTTPS_PRE))) {
                throw new BusinessException("外链必须以http://或者https://开头");
            }
        }
        //一级目录的情况
        if (resource.getPid().equals(0L)) {
            resource.setPid(null);
        }
        Menu sourceMenu = menuMapper.selectById(resource.getId());
        Menu menu2 = menuMapper.selectById(resource.getId());

        //组件不允许重复
        if (StrUtil.isNotBlank(resource.getComponentName())) {
            Menu menu1 = menuMapper.findByComponentName(resource.getComponentName());
            if (menu1 != null && !menu1.getId().equals(sourceMenu.getId())) {
                throw new BusinessException("组件名称：" + resource.getComponentName() + "已存在,请重新输入");
            }
        }

        //记录父节点的ID
        Long oldPid = menu2.getPid();
        Long newPid = resource.getPid();

        menu2.setTitle(resource.getTitle());
        menu2.setComponent(resource.getComponent());
        menu2.setPath(resource.getPath());
        menu2.setIcon(resource.getIcon());
        menu2.setIFrame(resource.getIFrame());
        menu2.setPid(resource.getPid());
        menu2.setMenuSort(resource.getMenuSort());
        menu2.setCache(resource.getCache());
        menu2.setHidden(resource.getHidden());
        menu2.setComponentName(resource.getComponentName());
        menu2.setPermission(resource.getPermission());
        menu2.setType(resource.getType());
        saveOrUpdate(menu2);

        //计算父级菜单节点数目
        updateSubCnt(oldPid);
        updateSubCnt(newPid);
        //清理缓存
        delCaches(resource.getId());
    }

    /**
     * 删除菜单
     *
     * @param ids 主键合集
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Set<Long> ids) {
        Set<Menu> menuSet = new HashSet<>();
        for (Long id : ids) {
            List<Menu> menuList = getMenus(id);
            menuSet.add(menuMapper.selectById(id));
            menuSet = getChildMenus(menuList, menuSet);
        }
        for (Menu menu : menuSet) {
            //清理缓存
            delCaches(menu.getId());
            roleMenuMapper.deleteByMenuId(menu.getId());
            menuMapper.deleteById(menu.getId());
            updateSubCnt(menu.getPid());
        }
    }

    /**
     * 导出菜单
     *
     * @param response    http回应
     * @param queryObject 导出条件
     */
    @Override
    public void download(HttpServletResponse response, MenuQueryObject queryObject) {
        List<Map<String, Object>> list = new ArrayList<>();
        List<Menu> menus = menuMapper.findAll(queryObject);
        for (Menu menu : menus) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("菜单标题", menu.getTitle());
            map.put("菜单类型", menu.getType() == null ? "目录" : menu.getType() == 1 ? "菜单" : "按钮");
            map.put("权限标识", menu.getPermission());
            map.put("外链菜单", menu.getIFrame() ? "是" : "否");
            map.put("菜单可见", menu.getHidden() ? "否" : "是");
            map.put("是否缓存", menu.getCache() ? "是" : "否");
            map.put("创建日期", menu.getCreateTime());
            list.add(map);
        }
        try {
            FileUtil.downloadExcel(list, response);
        } catch (IOException e) {
            throw new BusinessException("由于:" + e.getMessage() + "下载失败");
        }
    }

    /**
     * 懒加载菜单数据
     *
     * @param pid pid
     * @return 菜单集合
     */
    @Override
    public List<Menu> getMenus(Long pid) {
        List<Menu> menus;
        if (ObjectUtil.isNotNull(pid) && !pid.equals(0L)) {
            menus = menuMapper.findByPidOrderByMenuSort(pid);
        } else {
            menus = menuMapper.findByPidIsNullOrderByMenuSort();
        }
        return menus;
    }


    /**
     * 获取所有子节点，包含自身ID
     *
     * @param menuList /
     * @param menuSet  /
     * @return /
     */
    @Override
    public Set<Menu> getChildMenus(List<Menu> menuList, Set<Menu> menuSet) {
        for (Menu menu : menuList) {
            menuSet.add(menu);
            List<Menu> menus = menuMapper.findByPidOrderByMenuSort(menu.getId());
            if (menus != null && menus.size() != 0) {
                getChildMenus(menus, menuSet);
            }
        }
        return menuSet;
    }

    /**
     * 获取当前用户的实体对象
     *
     * @return menu
     */
    @Override
    public List<MenuDto> findByUser(Long currentUserId) {
        List<RoleSmallDto> roles = roleService.findRolesByUserId(currentUserId);
        Set<Long> roleIds = roles.stream().map(RoleSmallDto::getId).collect(Collectors.toSet());
        LinkedHashSet<Menu> menus = menuMapper.findByRolesIdsAndTypeNot(roleIds, 2);
        return MenuDto.menuListToMenuDtoList(menus);
    }

    /**
     * @param menuQueryObject 查询菜单
     * @return 第一级菜单树
     */
    @Override
    public List<MenuDto> queryMenu(MenuQueryObject menuQueryObject) {
        if (ObjectUtil.isEmpty(menuQueryObject.getPid())) {
            menuQueryObject.setPidIsNull(true);
        }
        Set<Menu> menus = menuMapper.queryTopMenuTree(menuQueryObject);
        return MenuDto.menuListToMenuDtoList(menus).stream().
                sorted(Comparator.comparing(MenuDto::getMenuSort)).
                collect(Collectors.toList());
    }

    /**
     * 通过Pid获取子菜单树
     *
     * @param pid 上一级菜单Id
     * @return 子菜单树
     */
    @Override
    public List<Menu> getMenusTrees(Long pid) {

        List<Menu> menus;
        QueryWrapper<Menu> menuQueryWrapper = new QueryWrapper<>();
        if (ObjectUtil.isNotEmpty(pid) && !pid.equals(0L)) {
            menuQueryWrapper.lambda().eq(Menu::getPid, pid).orderByAsc(Menu::getMenuSort);
        } else {
            menuQueryWrapper.lambda().isNull(Menu::getPid).orderByAsc(Menu::getMenuSort);
        }
        menus = menuMapper.selectList(menuQueryWrapper);
        return menus;
    }

    /**
     * 根据ID获取上级数据(编辑菜单用)
     *
     * @param ids 主键
     * @return 菜单树
     */
    @Override
    public List<?> getMenuSuperior(List<Long> ids) {
        Set<Menu> menus = new LinkedHashSet<>();
        if (CollectionUtil.isNotEmpty(ids)) {
            for (Long id : ids) {
                Menu menu = getById(id);
                List<Menu> superior = getSuperior(menu, new ArrayList<>());
                for (Menu data : superior) {
                    if (data.getId().equals(menu.getPid())) {
                        data.setSubCount(data.getSubCount() - 1);
                    }
                }
                menus.addAll(superior);
            }
            //编辑菜单时不显示自己以及自己下级的数据，避免出现PID数据循环
            menus = menus.stream().filter(i -> !ids.contains(i.getId()) || ids.contains(i.getPid())).collect(Collectors.toSet());
            return buildTrees(MenuDto.menuListToMenuDtoList(new ArrayList<>(menus)));
        }
        return menuMapper.findByPidIsNullOrderByMenuSort();
    }

    /**
     * 获取菜单子节点Id
     *
     * @param id 该菜单ID
     * @return 该级菜单的子节点集合
     */
    @Override
    public Set<Long> getMenuChildIds(Long id) {
        Set<Menu> menuSet = new HashSet<>();
        List<Menu> menuList = getMenus(id);
        menuSet.add(getById(id));
        menuSet = getChildMenus(menuList, menuSet);
        return menuSet.stream().map(Menu::getId).collect(Collectors.toSet());
    }

    private List<Menu> getSuperior(Menu menu, List<Menu> menus) {
        if (menu.getPid() == null) {
            menus.addAll(menuMapper.findByPidIsNullOrderByMenuSort());
            return menus;
        }
        menus.addAll(menuMapper.findByPidOrderByMenuSort(menu.getPid()));
        return getSuperior(menuMapper.selectById(menu.getPid()), menus);
    }

    private void updateSubCnt(Long menuId) {
        if (menuId != null) {
            int countByPid = menuMapper.countByPid(menuId);
            menuMapper.updateSubCntById(countByPid, menuId);
        }
    }

    /**
     * 清理缓存
     *
     * @param id 菜单ID
     */
    public void delCaches(Long id) {
        List<User> users = userMapper.findByMenuId(id);
        redisUtils.del(CacheKey.MENU_ID + id);
        redisUtils.delByKeys(CacheKey.MENU_USER, users.stream().map(User::getId).collect(Collectors.toSet()));
        // 清除 Role 缓存
        List<Role> roles = roleService.findByMenuId(id);
        redisUtils.delByKeys(CacheKey.ROLE_ID, roles.stream().map(Role::getId).collect(Collectors.toSet()));
    }
}
